package com.bite.friend.service.user.impl;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.bite.common.core.constants.CacheConstants;
import com.bite.common.core.constants.Constants;
import com.bite.common.core.constants.HttpConstants;
import com.bite.common.core.domain.LoginUser;
import com.bite.common.core.domain.R;
import com.bite.common.core.domain.vo.LoginUserVO;
import com.bite.common.core.enums.ResultCode;
import com.bite.common.core.enums.UserIdentity;
import com.bite.common.core.enums.UserStatus;
import com.bite.common.core.utils.ThreadLocalUtil;
import com.bite.common.message.service.AliSmsService;
import com.bite.common.redis.service.RedisService;
import com.bite.common.security.exception.ServiceException;
import com.bite.common.security.service.TokenService;
import com.bite.friend.domain.user.dto.UserUpdateDTO;
import com.bite.friend.domain.user.vo.UserVO;
import com.bite.friend.manager.UserCacheManager;
import com.bite.friend.mapper.user.UserMapper;
import com.bite.friend.service.user.IUserService;
import com.bite.friend.domain.user.User;
import com.bite.friend.domain.user.dto.UserDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Service
public class UserServiceImpl implements IUserService {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RedisService redisService;
    @Autowired
    private AliSmsService aliSmsService;
    @Autowired
    private TokenService tokenService;
    @Value("${sms.code-expiration:5}")
    private Long phoneCodeExpiration;
    @Value("${sms.send-limit:6}")
    private Integer sendLimit;
    @Value("${jwt.secret}")
    private String secret;
    @Value("${sms.is-end:false}")
    private boolean isSend;    //开关打开就是true，关闭就是false
    @Value("${file.oss.downloadUrl}")
    private String downloadUrl;
    @Autowired
    private UserCacheManager userCacheManager;

    @Override
    public boolean sendCode(UserDTO userDTO) {
        //先判断是否是一个真正的手机号
        if (!checkPhone(userDTO.getPhone())) {
            throw new ServiceException(ResultCode.FAILED_USER_PHONE);
        }
        //根据key获取当前的剩余时间,跟一分钟做比较
        Long expire = redisService.getExpire(userDTO.getPhone(), TimeUnit.SECONDS);
        //如何计算时间呢？
        if (expire != null && (phoneCodeExpiration * 60 - expire) < 60) {
            throw new ServiceException(ResultCode.FAILED_FREQUENT);
        }
        //每天的验证次数有一个限制，并且第二天的计数应该清0，重新开始计数，计数，存储，如何存，存在哪里，
        // redis String key: c:t :手机号    根据人来区分，所以用手机号
        String phoneCodeKey = getPhoneCodeKey(userDTO.getPhone());
        String codeTimeKey = getCodeTimeKey(userDTO.getPhone());
        //获取已经请求的次数与50进行比较,如果大于50抛出异常，如果不大于限制，正常执行后续逻辑，并且将获取计数+1
        Long sendTimes = redisService.getCacheObject(codeTimeKey, Long.class);
        //看当天次数是否到达上限的次数
        if (sendTimes != null && sendTimes >= sendLimit) {
            throw new ServiceException(ResultCode.FAILED_TIME_LIMIT);
        }
        //开关打开生成随机的，不打开就是默认的123456
        String code = isSend ? RandomUtil.randomNumbers(6) : Constants.DEFAULT_CODE;
        //存储到redis 数据结构String key p:c手机号 value:code
        //将数据存储到redis中，生成验证码，放入redis里面
        redisService.setCacheObject(phoneCodeKey, code, phoneCodeExpiration, TimeUnit.MINUTES);
        //避免一直点击验证码导致服务器压垮了
        if (isSend) {
            boolean sendMobileCode = aliSmsService.sendMobileCode(userDTO.getPhone(), code);
            if (!sendMobileCode) {
                throw new ServiceException(ResultCode.FAILED_SEND_CODE);
            }
        }
        //redis进行计数
        redisService.increment(codeTimeKey);
        //记录当天第一次发起获取验证码的请求,和0点00分，计算第二天的时间（设计有效时间，所以需要动态的去计算有效的时间)
        if (sendTimes == null) {
            //有效时常
            long seconds = ChronoUnit.SECONDS.between(LocalDateTime.now(),
                    LocalDateTime.now().plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0));
            redisService.expire(codeTimeKey, seconds, TimeUnit.SECONDS);
        }
        return true;
    }



    @Override
    public String codeLogin(String phone, String code) {
        //完成验证码的比对
        checkCode(phone, code);
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getPhone, phone));
        if (user == null) {
            //新用户，执行注册逻辑，
            //先完成验证码的比对，通过手机号拿到key，然后比对验证码，如果比对成功，往系统中新增一个用户(向数据库的用户表插入一条数据即可)
            user = new User();
            user.setPhone(phone);
            user.setStatus(UserStatus.Normal.getValue());
            userMapper.insert(user);
        }
        //将新用户变为老用户
        //说明是老用户
        return tokenService.createToken(user.getUserId(), secret, UserIdentity.ORDINARY.getValue(), user.getNickName(), user.getHeadImage());
    }

    //B端与c端代码的注销功能完全一致
    @Override
    public boolean logout(String token) {
        if (StrUtil.isNotEmpty(token) &&
                token.startsWith(HttpConstants.PREFIX)) {
            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
        }
        return tokenService.deleteLoginUser(token, secret);

    }

    @Override
    public R<LoginUserVO> info(String token) {
        if (StrUtil.isNotEmpty(token) && token.startsWith(HttpConstants.PREFIX)) {
            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
        }
        LoginUser loginUser = tokenService.getLoginUser(token, secret);
        if (loginUser == null) {
            return R.fail();
        }
        LoginUserVO loginUserVO = new LoginUserVO();
        loginUserVO.setNickName(loginUser.getNickName());
        //为什么要进行拼接，因为用户头像的前半部分都是这样，假如每个都是存完整地址，那么前半部分都是一样，那么是不是前半部分都一样，那么就存一份不是就行了吗
        //比如就放在一个统一存储一个地方，比如放到nacos里面
        if(StrUtil.isNotEmpty(loginUser.getHeadImage())) {
            loginUserVO.setHeadImage(downloadUrl + loginUser.getHeadImage());
        }
        return R.ok(loginUserVO);
    }

    @Override
    public UserVO detail() {
        Long userId = ThreadLocalUtil.get(Constants.USER_ID, Long.class);
        if (userId == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        UserVO userVO = userCacheManager.getUserById(userId);
        if (userVO == null) {
            //你登录了，但是没有用户信息，用户可以去联系管理员
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        if (StrUtil.isNotEmpty(userVO.getHeadImage())) {
            userVO.setHeadImage(downloadUrl + userVO.getHeadImage());
        }
        return userVO;
    }

    @Override
    public int edit(UserUpdateDTO userUpdateDTO) {
        User user = checkUser();
        //赋值
        user.setNickName(userUpdateDTO.getNickName());
        user.setSex(userUpdateDTO.getSex());
        user.setSchoolName(userUpdateDTO.getSchoolName());
        user.setMajorName(userUpdateDTO.getMajorName());
        user.setPhone(userUpdateDTO.getPhone());
        user.setEmail(userUpdateDTO.getEmail());
        user.setWechat(userUpdateDTO.getWechat());
        user.setIntroduce(userUpdateDTO.getIntroduce());
        //更新用户缓存
          refreshUser(user);
        return userMapper.updateById(user);
    }

    //文件名存到用户表中
    @Override
    public int updateHeadImage(String headImage) {
        User user = checkUser();
        user.setHeadImage(headImage);
        //更新用户缓存
        refreshUser(user);
        return userMapper.updateById(user);
    }
    public User checkUser(){
        Long userId = ThreadLocalUtil.get(Constants.USER_ID, Long.class);
        if (userId == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        User user = userMapper.selectById(userId);
        if (user == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        return user;
    }

    public void refreshUser(User user){
        userCacheManager.refreshUser(user);
        tokenService.refreshLoginUser(user.getNickName(),user.getHeadImage(),
                ThreadLocalUtil.get(Constants.USER_KEY, String.class));
    }
    public static boolean checkPhone(String phone) {
        Pattern regex = Pattern.compile("^1[2|3|4|5|6|7|8|9][0-9]\\d{8}$");
        Matcher m = regex.matcher(phone);
        return m.matches();
    }
    public void checkCode(String phone, String code) {
        String phoneCodeKey = getPhoneCodeKey(phone);
        //判断验证码是否无效,因为我假如是新用户，从redis里面无法获取到数据，所以会报一个验证码无效到错误
        String cacheCode = redisService.getCacheObject(phoneCodeKey, String.class);
        if (StrUtil.isEmpty(cacheCode)) {
            throw new ServiceException(ResultCode.FAILED_INVALID_CODE);
        }

        if (!cacheCode.equals(code)) {
            //看是否一样，不一样报错
            throw new ServiceException(ResultCode.FAILED_ERROR_CODE);
        }
        //验证码比对成功,生成token，我们完成登录之后，这个验证码应该去给他删除，他的使命就应该没了(不能一直存里面
        //会出现误导，也会浪费缓存空间
        redisService.deleteObject(phoneCodeKey);
    }
    private String getCodeTimeKey(String phone) {
        return CacheConstants.CODE_TIME_KEY + phone;
    }

    private String getPhoneCodeKey(String phone) {
        return CacheConstants.PHONE_CODE_KEY + phone;
    }



}
